/*
 * MIT License
 *
 * Copyright (c) 2019 Mr.css
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

package cn.seaboot.commons.handler;

import cn.seaboot.commons.exception.HandlerException;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadFactory;

/**
 * 消息轮询
 * 队列和线程是绑定在一起的，默认情况下，1个Looper绑定1个线程，不存在资源竞争的情况。
 * <p>
 * 不适合多个线程绑定同一个队列，因为阻塞队列本身的设计问题，
 * 在take()不到数据的时候，线程会处于阻塞状态，在获取到数据之前，没办法强制终止线程，
 * 目前是通过放置哑元元素的方式，退出程序。
 * <p>
 *
 * @author Mr.css on 2018年3月24日
 */
public class Looper {
    /**
     * 与Looper绑定的工作线程，队列上的所有消息最终都在这个线程上处理
     */
    private final Thread thread;
    /**
     * 阻塞队列
     */
    BlockingQueue<Message> queue;

    /**
     * 默认使用优先队列，根据消息的时间戳先后进行排序，时间戳早的先执行，
     * 可以指定FIFO的阻塞队列。
     *
     * @param queue 阻塞队列
     */
    private Looper(BlockingQueue<Message> queue) {
        thread = Thread.currentThread();
        if (queue == null) {
            this.queue = new DelayQueue<>();
        } else {
            this.queue = queue;
        }
    }

    /**
     * 获取与之绑定在一起的队列
     *
     * @return -
     */
    public Thread getBindingThread() {
        return thread;
    }


    //static start -----------------------------------

    /**
     * 使用ThreadLocal管理所有线程中的Looper，并且保证每个线程只有1个Looper
     */
    private static final ThreadLocal<Looper> LOOPER_THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 尽管这是一个静态的方法，但是每个线程只能取出自己的Lopper
     *
     * @return Looper
     */
    public static Looper myLooper() {
        return LOOPER_THREAD_LOCAL.get();
    }

    /**
     * 使用默认的延迟队列，启动轮询线程
     */
    public static void prepare() {
        prepare(null);
    }

    /**
     * 默认使用延迟队列，使用此函数自选阻塞队列
     *
     * @param mQueue 指定一种类型的消息队列
     */
    public static void prepare(BlockingQueue<Message> mQueue) {
        if (LOOPER_THREAD_LOCAL.get() != null) {
            throw new HandlerException("Only one Looper may be created per thread");
        }
        LOOPER_THREAD_LOCAL.set(new Looper(mQueue));
    }

    /**
     * 启动消息轮询，经过此函数，线程进入工作轮询。
     * <p>
     * 正常情况下，跳出循环之后，此函数无法重新调用，Lopper 是无法重新启动循环的；
     * 因为 Lopper 是和工作线程绑定在一起的，结束循环之后，
     * 工作线程就已经结束工作，该线程会被回收或者销毁。
     * <p>
     * 中断循环发方式有两种：
     * 1、抛出异常；
     * 2、放置哑元元素。
     * <p>
     * 延迟队列的中断：
     * 延迟队列是比较特殊的，放置哑元元素之后，只要工作线程完成当前工作，就会立刻读取到哑元元素，
     * 此时，工作线程会结束工作，而队列内可能包含未被处理的消息，业务上需要做好标记，在下次启动时，重新启动那些消息。
     */
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new HandlerException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final BlockingQueue<Message> q = me.queue;

        for (; ; ) {
            try {
                Message item = q.take();
                if (!item.isDummy()) {
                    Object msg = item.getMsg();
                    item.getHandler().dispatchMessage(msg);
                } else {
                    // 遇到哑元元素，跳出循环
                    break;
                }
            } catch (InterruptedException e) {
                throw new HandlerException("Looper interrupted exception", e);
            }
        }
    }

    /**
     * 通过 ThreadFactory 构造一个工作线程，并且在该线程上启动 Lopper。
     *
     * @param mQueue queue，注意需要调用 Handler 有参构造函数，将 queue 绑定到 Handler
     * @throws InterruptedException -
     */
    public static void loopOnDaemonThread(ThreadFactory threadFactory, BlockingQueue<Message> mQueue)
            throws InterruptedException {
        synchronized (Looper.class) {
            final Semaphore available = new Semaphore(1);
            // 因为初始化需要一定的时间，这里设置一个信号量
            available.acquire();

            Thread thread = threadFactory.newThread(() -> {
                // Looper绑定到当前线程，Thread.currentThread()作为专门处理消息的工作线程
                Looper.prepare(mQueue);
                // 释放信号量
                available.release();
                // Lopper的队列循环启动
                Looper.loop();

            });

            //设置为守护线程
            thread.setDaemon(true);
            thread.start();
            // 阻塞直到 Looper 启动
            available.acquireUninterruptibly();
        }
    }

    //static end -----------------------------------
}
